package optiondef

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"hash"
	"io"
	"net/http"
	"photowall/pkg/config"
	"photowall/pkg/log"
	"photowall/pkg/options"
	"photowall/pkg/service"
	"sync"
	"time"

	"github.com/go-redis/redis"
)

// 查看redis中的微信用户键是否过期，以此确定自定义登录状态
func checkWxUserIsLoggedByRedis(session string) (res bool, err error) {
	key := config.GetConfig().WechatUserConfig.RedisSessionMark + "@" + session

	exist, err := options.GetRedisClient().Exists(key).Result()
	if err != nil {
		if err == redis.Nil {
			return false, nil
		}
		log.GetRedisLogger().Error(fmt.Sprintf("func:CheckIfWechatUserLogged can not get wechatuser exist :%v", key))
		return false, err
	}

	if exist == 1 {
		return true, nil
	}

	return false, nil
}

// description: 刷新微信登录状态
// param {string} userID
// return {*}

func markWechatUserIsLoggedByRedis(session string, openid string, sessionKey string) bool {
	key := config.GetConfig().WechatUserConfig.RedisSessionMark + "@" + session
	redis_openid_mark := config.GetConfig().WechatUserConfig.RedisOpenidMark
	redis_session_key_mark := config.GetConfig().WechatUserConfig.RedisSessionKeyMark

	fields := map[string]interface{}{
		redis_openid_mark:      openid,
		redis_session_key_mark: sessionKey,
	}

	err := options.GetRedisClient().HMSet(key, fields).Err()
	if err != nil {
		log.GetRedisLogger().Error(fmt.Sprintf("can not HMSet for func markWechatUserIsLoggedByRedis %v", fields))
		return false
	}

	expiration := time.Duration(config.GetConfig().WechatUserConfig.RedisSessionExpiretime) * time.Minute
	if err := options.GetRedisClient().Expire(key, expiration).Err(); err != nil {
		log.GetRedisLogger().Error(fmt.Sprintf("Failed to set expiration time for func markWechatUserIsLoggedByRedis: %v", err))
		return false
	}

	return true
}

// 从redis中删除session代表的key
func logoutWechatUserBysessionInRedis(session string) (bool, error) {
	key := config.GetConfig().WechatUserConfig.RedisSessionMark + "@" + session

	err := options.GetRedisClient().Del(key).Err()
	if err != nil {
		log.GetMainLogger().Info(fmt.Sprintf(`can not delete not exist key, %v`, key))
		return false, err
	}

	return true, nil
}

// 刷新wxuser在redis的过期时间，延长登录时间
func refreashWxUserLogginExpiretimeBySessionImRedis(session string) (bool, error) {
	key := config.GetConfig().WechatUserConfig.RedisSessionMark + "@" + session

	expiration := time.Duration(config.GetConfig().WechatUserConfig.RedisSessionExpiretime) * time.Minute
	err := options.GetRedisClient().Expire(key, expiration).Err()
	if err != nil {
		log.GetMainLogger().Error(fmt.Sprintf("Error setting expiration: %v", err))
		return false, err
	}

	return true, nil
}

//description: 输入一个userID添加微信用户到登录状态，并返回session
//param {string} userID
//return {*}

// func addWechatUserSessionIntoRedis(userID string) string {

// }

// 使用jscode请求，记录到登录状态，并注册一个session, session 保存在redis中
func wechatLogin(jsCode string) (string, error) {
	if len(jsCode) == 0 {
		return "", fmt.Errorf("js_code is invalid: %v", jsCode)
	}

	// 获取loginres
	loginRequestRes, err := syncJsCodeLogin(jsCode)
	if err != nil {
		log.GetMainLogger().Error(fmt.Sprintf(`js_code request failed, %v, %v`, loginRequestRes, err))
		return "", err
	}
	if loginRequestRes.ErrorCode != 0 {
		log.GetMainLogger().Info(fmt.Sprintf(`sync loging by jscode return errcode, %v`, loginRequestRes))
		return "", err
	}

	exist, err := getWxUserdbService().IsWxUserInDb(&WxUserInfo{OpenID: loginRequestRes.OpenID})
	if err != nil {
		log.GetDatabaseLogger()
		log.GetMainLogger().Error(fmt.Sprintf(`find user in db failed, %v, %v`, loginRequestRes, err))
		return "", err
	}

	// 不存在，则加入数据库中
	if !exist {
		var wechatUser WxUserInfo
		wechatUser.OpenID = loginRequestRes.OpenID
		wechatUser.ID = service.GetGuidForWechatUserID()
		wechatUser.UnionID = loginRequestRes.UnionID
		wechatUser.SessionKey = loginRequestRes.SessionKey
		wechatUser.PhoneNumber = ""
		wechatUser.Password = ""
		wechatUser.UserName = loginRequestRes.OpenID

		ok, err := GetWxUserService().WxRegisterUser(&wechatUser)
		if err != nil {
			log.GetMainLogger().Error(fmt.Sprintf(`func wechatLogin can not add user %v`, wechatUser))
			return "", err
		}

		if !ok {
			return "", fmt.Errorf(`func wechatLogin add wechatuser failed, %v`, wechatUser)
		}
	}

	// 已经存在该openid,将微信用户添加到登录状态，并更新其session_key 和openid进去
	session := service.GetGuidForSession(loginRequestRes.OpenID)
	ok := markWechatUserIsLoggedByRedis(session, loginRequestRes.OpenID, loginRequestRes.SessionKey)
	if !ok {
		return "", fmt.Errorf(`func wechatLogin can not markuserlogged, %v`, loginRequestRes)
	}

	return session, nil
}

//description: 同步从jsCode获取openid的方法
//param {string} jsCode
//return {*}

type loginRequestRes struct {
	SessionKey string `form:"username" json:"session_key" uri:"session_key" xml:"user"  binding:"required"`
	UnionID    string `json:"unionid" binding:"required"`
	ErrMsg     string `josn:"errmsg" binding:"required"`
	OpenID     string `json:"openid" binding:"required"`
	ErrorCode  int32  `json:"errcode" binding:"required"`
}

func syncJsCodeLogin(jsCode string) (*loginRequestRes, error) {
	// 请求并获取LoginRequestRes
	var loginPath = "https://api.weixin.qq.com/sns/jscode2session?" + "appid=" +
		config.GetConfig().WechatInfo.AppID + "&secret=" + config.GetConfig().WechatInfo.AppSecret +
		"&js_code=" + jsCode + "&grant_type=" + config.GetConfig().WechatInfo.AppGranttype

	resp, err := http.Get(loginPath)
	if err != nil {
		log.GetMainLogger().Error("wechat logging failed, info: " + loginPath)
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		log.GetMainLogger().Error("resp.Body read failed!")
		return nil, err
	}
	log.GetMainLogger().Info(`js_code request return:` + string(body))

	var loginRequestRes loginRequestRes
	err = json.Unmarshal(body, &loginRequestRes)
	if err != nil {
		log.GetMainLogger().Error("josn Unmarshal failed" + string(body))
		return nil, err
	}

	return &loginRequestRes, nil
}

type sessionKeyResponse struct {
	ErrCode int    `json:"errcode"`
	ErrMsg  string `json:"errmsg"`
}

var (
	hashInstance             hash.Hash
	hashInstanceMutex        sync.Mutex
	hashInstanceInitSyncOnce sync.Once
)

// SignWithSessionKey 使用 session_key 对字符串进行签名
func SignWithSessionKey(data string, sessionKey string) string {
	hashInstanceInitSyncOnce.Do(func() {
		// 创建一个 HMAC 对象
		hashInstance = hmac.New(sha256.New, []byte(sessionKey))
	})
	hashInstanceMutex.Lock()
	defer hashInstanceMutex.Unlock()

	hashInstance.Reset()
	// 写入待签名数据
	hashInstance.Write([]byte(data))

	// 计算 HMAC
	signature := hashInstance.Sum(nil)

	// 返回签名的十六进制字符串
	return hex.EncodeToString(signature)
}

// 确认一个sessionkey是否依旧有效，以此来验证该微信用户是否依旧真实处于登录状态
func checkSessionKeyValid(sessionKey string, openID string) bool {
	signaturedStr := SignWithSessionKey("", sessionKey)

	// 构造请求的body
	requestUrl := fmt.Sprintf("https://api.weixin.qq.com/wxa/checksession?access_token=%s&signature=%s&openid=%s&sig_method=%s",
		service.GetAccessToken(), signaturedStr, openID, "hmac_sha256")
	bodyData := []byte(requestUrl)

	// 请求API
	response, err := http.Post(requestUrl, "application/json", bytes.NewBuffer(bodyData))
	if err != nil {
		log.GetMainLogger().Error(fmt.Sprintf("GetAccessToken http error:%s", err))
		return false
	}
	defer response.Body.Close()

	if response.StatusCode/100 != 2 {
		log.GetMainLogger().Error(fmt.Sprintf("access_token Get request stauts is not 200 %v", response.StatusCode))
	}

	// 读取响应内容
	body, err := io.ReadAll(response.Body)
	if err != nil {
		log.GetMainLogger().Error(fmt.Sprintf("access_token read io failed:%s", err))
		return false
	}

	// 解析结果到结构体
	var sessionKeyResponse sessionKeyResponse
	err = json.Unmarshal(body, &sessionKeyResponse)
	if err != nil {
		log.GetMainLogger().Error(fmt.Sprintf("can not Unmarshal json data %s ,err is :%v",
			string(body), err.Error()))
	}

	if sessionKeyResponse.ErrCode == 0 {
		return true
	}

	return false
}

func getWxUserOpenIDBySessionInRedis(session string) (string, error) {

	key := config.GetConfig().WechatUserConfig.RedisSessionMark + "@" + session
	redis_openid_mark := config.GetConfig().WechatUserConfig.RedisOpenidMark

	res, err := options.GetRedisClient().HMGet(key, redis_openid_mark).Result()
	if err != nil {
		if err == redis.Nil {
			return "", nil
		}

		log.GetRedisLogger().Error(fmt.Sprintf("can not HMSet for func markWechatUserIsLoggedByRedis %v", err))
		return "", err
	}

	if open_id, ok := res[0].(string); !ok {
		return "", fmt.Errorf(`func getWxUserOpenIDBySessionInRedis: bad redis res datatype`)
	} else {
		return open_id, nil
	}

}
